/**
 * 
 */
package org.arch.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.StringTokenizer;

/**
 * @author wqy
 * 
 */
public class NetworkHelper
{


	/**
	 * The {@link InetAddress} representing the host machine
	 * <p/>
	 * We cache this because some machines take almost forever to return from
	 * {@link InetAddress}.getLocalHost(). This may be due to incorrect
	 * configuration of the hosts and DNS client configuration files.
	 */
	public static final InetAddress LOCALHOST;

	/**
	 * The loopback {@link NetworkInterface} on the current machine
	 */
	public static final NetworkInterface LOOPBACK_IF;

	/**
	 * The SOMAXCONN value of the current machine. If failed to get the value,
	 * 3072 is used as a default value.
	 */
	public static final int SOMAXCONN;

	static
	{
		// Start the process of discovering localhost
		InetAddress localhost;
		try
		{
			localhost = InetAddress.getLocalHost();
			validateHost(localhost);
		}
		catch (IOException e)
		{
			// The default local host names did not work. Try hard-coded IPv4
			// address.
			try
			{
				localhost = InetAddress
				        .getByAddress(new byte[] { 127, 0, 0, 1 });
				validateHost(localhost);
			}
			catch (IOException e1)
			{
				// The hard-coded IPv4 address did not work. Try hard coded IPv6
				// address.
				try
				{
					localhost = InetAddress.getByAddress(new byte[] { 0, 0, 0,
					        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 });
					validateHost(localhost);
				}
				catch (IOException e2)
				{
					throw new Error(
					        "Failed to resolve localhost - incorrect network configuration?",
					        e2);
				}
			}
		}

		LOCALHOST = localhost;

		// Prepare to get the local NetworkInterface
		NetworkInterface loopbackInterface;

		try
		{
			// Automatically get the loopback interface
			loopbackInterface = NetworkInterface.getByInetAddress(LOCALHOST);
		}
		catch (SocketException e)
		{
			// No? Alright. There is a backup!
			loopbackInterface = null;
		}

		// Check to see if a network interface was not found
		if (loopbackInterface == null)
		{
			try
			{
				// Start iterating over all network interfaces
				for (Enumeration<NetworkInterface> interfaces = NetworkInterface
				        .getNetworkInterfaces(); interfaces.hasMoreElements();)
				{
					// Get the "next" interface
					NetworkInterface networkInterface = interfaces
					        .nextElement();

					// Check to see if the interface is a loopback interface
					if (networkInterface.isLoopback())
					{
						// Phew! The loopback interface was found.
						loopbackInterface = networkInterface;
						// No need to keep iterating
						break;
					}
				}
			}
			catch (SocketException e)
			{
				// Nope. Can't do anything else, sorry!
				e.printStackTrace();
			}
		}

		// Set the loopback interface constant
		LOOPBACK_IF = loopbackInterface;

		int somaxconn = 3072;
		BufferedReader in = null;
		try
		{
			in = new BufferedReader(new FileReader(
			        "/proc/sys/net/core/somaxconn"));
			somaxconn = Integer.parseInt(in.readLine());
		}
		catch (Exception e)
		{
			// Failed to get SOMAXCONN
		}
		finally
		{
			if (in != null)
			{
				try
				{
					in.close();
				}
				catch (Exception e)
				{
					// Ignored.
				}
			}
		}

		SOMAXCONN = somaxconn;
	}

	private static void validateHost(InetAddress host) throws IOException
	{
		ServerSocket ss = null;
		Socket s1 = null;
		Socket s2 = null;
		try
		{
			ss = new ServerSocket();
			ss.setReuseAddress(false);
			ss.bind(new InetSocketAddress(host, 0));
			s1 = new Socket(host, ss.getLocalPort());
			s2 = ss.accept();
		}
		finally
		{
			if (s2 != null)
			{
				try
				{
					s2.close();
				}
				catch (IOException e)
				{
					// Ignore
				}
			}
			if (s1 != null)
			{
				try
				{
					s1.close();
				}
				catch (IOException e)
				{
					// Ignore
				}
			}
			if (ss != null)
			{
				try
				{
					ss.close();
				}
				catch (IOException e)
				{
					// Ignore
				}
			}
		}
	}

	/**
	 * Creates an byte[] based on an ipAddressString. No error handling is
	 * performed here.
	 */
	public static byte[] createByteArrayFromIpAddressString(
	        String ipAddressString)
	{

		if (isValidIpV4Address(ipAddressString))
		{
			StringTokenizer tokenizer = new StringTokenizer(ipAddressString,
			        ".");
			String token;
			int tempInt;
			byte[] byteAddress = new byte[4];
			for (int i = 0; i < 4; i++)
			{
				token = tokenizer.nextToken();
				tempInt = Integer.parseInt(token);
				byteAddress[i] = (byte) tempInt;
			}

			return byteAddress;
		}

		if (isValidIpV6Address(ipAddressString))
		{
			if (ipAddressString.charAt(0) == '[')
			{
				ipAddressString = ipAddressString.substring(1,
				        ipAddressString.length() - 1);
			}

			StringTokenizer tokenizer = new StringTokenizer(ipAddressString,
			        ":.", true);
			ArrayList<String> hexStrings = new ArrayList<String>();
			ArrayList<String> decStrings = new ArrayList<String>();
			String token = "";
			String prevToken = "";
			int doubleColonIndex = -1; // If a double colon exists, we need to
			// insert 0s.

			// Go through the tokens, including the seperators ':' and '.'
			// When we hit a : or . the previous token will be added to either
			// the hex list or decimal list. In the case where we hit a ::
			// we will save the index of the hexStrings so we can add zeros
			// in to fill out the string
			while (tokenizer.hasMoreTokens())
			{
				prevToken = token;
				token = tokenizer.nextToken();

				if (":".equals(token))
				{
					if (":".equals(prevToken))
					{
						doubleColonIndex = hexStrings.size();
					}
					else if (!prevToken.isEmpty())
					{
						hexStrings.add(prevToken);
					}
				}
				else if (".".equals(token))
				{
					decStrings.add(prevToken);
				}
			}

			if (":".equals(prevToken))
			{
				if (":".equals(token))
				{
					doubleColonIndex = hexStrings.size();
				}
				else
				{
					hexStrings.add(token);
				}
			}
			else if (".".equals(prevToken))
			{
				decStrings.add(token);
			}

			// figure out how many hexStrings we should have
			// also check if it is a IPv4 address
			int hexStringsLength = 8;

			// If we have an IPv4 address tagged on at the end, subtract
			// 4 bytes, or 2 hex words from the total
			if (!decStrings.isEmpty())
			{
				hexStringsLength -= 2;
			}

			// if we hit a double Colon add the appropriate hex strings
			if (doubleColonIndex != -1)
			{
				int numberToInsert = hexStringsLength - hexStrings.size();
				for (int i = 0; i < numberToInsert; i++)
				{
					hexStrings.add(doubleColonIndex, "0");
				}
			}

			byte[] ipByteArray = new byte[16];

			// Finally convert these strings to bytes...
			for (int i = 0; i < hexStrings.size(); i++)
			{
				convertToBytes(hexStrings.get(i), ipByteArray, i * 2);
			}

			// Now if there are any decimal values, we know where they go...
			for (int i = 0; i < decStrings.size(); i++)
			{
				ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings
				        .get(i)) & 255);
			}
			return ipByteArray;
		}
		return null;
	}

	/**
	 * Converts a 4 character hex word into a 2 byte word equivalent
	 */
	private static void convertToBytes(String hexWord, byte[] ipByteArray,
	        int byteIndex)
	{

		int hexWordLength = hexWord.length();
		int hexWordIndex = 0;
		ipByteArray[byteIndex] = 0;
		ipByteArray[byteIndex + 1] = 0;
		int charValue;

		// high order 4 bits of first byte
		if (hexWordLength > 3)
		{
			charValue = getIntValue(hexWord.charAt(hexWordIndex++));
			ipByteArray[byteIndex] |= charValue << 4;
		}

		// low order 4 bits of the first byte
		if (hexWordLength > 2)
		{
			charValue = getIntValue(hexWord.charAt(hexWordIndex++));
			ipByteArray[byteIndex] |= charValue;
		}

		// high order 4 bits of second byte
		if (hexWordLength > 1)
		{
			charValue = getIntValue(hexWord.charAt(hexWordIndex++));
			ipByteArray[byteIndex + 1] |= charValue << 4;
		}

		// low order 4 bits of the first byte
		charValue = getIntValue(hexWord.charAt(hexWordIndex));
		ipByteArray[byteIndex + 1] |= charValue & 15;
	}

	static int getIntValue(char c)
	{

		switch (c)
		{
			case '0':
				return 0;
			case '1':
				return 1;
			case '2':
				return 2;
			case '3':
				return 3;
			case '4':
				return 4;
			case '5':
				return 5;
			case '6':
				return 6;
			case '7':
				return 7;
			case '8':
				return 8;
			case '9':
				return 9;
		}

		c = Character.toLowerCase(c);
		switch (c)
		{
			case 'a':
				return 10;
			case 'b':
				return 11;
			case 'c':
				return 12;
			case 'd':
				return 13;
			case 'e':
				return 14;
			case 'f':
				return 15;
		}
		return 0;
	}

	public static boolean isValidIpV6Address(String ipAddress)
	{
		int length = ipAddress.length();
		boolean doubleColon = false;
		int numberOfColons = 0;
		int numberOfPeriods = 0;
		int numberOfPercent = 0;
		StringBuilder word = new StringBuilder();
		char c = 0;
		char prevChar;
		int offset = 0; // offset for [] ip addresses

		if (length < 2)
		{
			return false;
		}

		for (int i = 0; i < length; i++)
		{
			prevChar = c;
			c = ipAddress.charAt(i);
			switch (c)
			{

			// case for an open bracket [x:x:x:...x]
				case '[':
					if (i != 0)
					{
						return false; // must be first character
					}
					if (ipAddress.charAt(length - 1) != ']')
					{
						return false; // must have a close ]
					}
					offset = 1;
					if (length < 4)
					{
						return false;
					}
					break;

				// case for a closed bracket at end of IP [x:x:x:...x]
				case ']':
					if (i != length - 1)
					{
						return false; // must be last charcter
					}
					if (ipAddress.charAt(0) != '[')
					{
						return false; // must have a open [
					}
					break;

				// case for the last 32-bits represented as IPv4
				// x:x:x:x:x:x:d.d.d.d
				case '.':
					numberOfPeriods++;
					if (numberOfPeriods > 3)
					{
						return false;
					}
					if (!isValidIp4Word(word.toString()))
					{
						return false;
					}
					if (numberOfColons != 6 && !doubleColon)
					{
						return false;
					}
					// a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with
					// an
					// IPv4 ending, otherwise 7 :'s is bad
					if (numberOfColons == 7 && ipAddress.charAt(offset) != ':'
					        && ipAddress.charAt(1 + offset) != ':')
					{
						return false;
					}
					word.delete(0, word.length());
					break;

				case ':':
					// FIX "IP6 mechanism syntax #ip6-bad1"
					// An IPV6 address cannot start with a single ":".
					// Either it can starti with "::" or with a number.
					if (i == offset
					        && (ipAddress.length() <= i || ipAddress
					                .charAt(i + 1) != ':'))
					{
						return false;
					}
					// END FIX "IP6 mechanism syntax #ip6-bad1"
					numberOfColons++;
					if (numberOfColons > 7)
					{
						return false;
					}
					if (numberOfPeriods > 0)
					{
						return false;
					}
					if (prevChar == ':')
					{
						if (doubleColon)
						{
							return false;
						}
						doubleColon = true;
					}
					word.delete(0, word.length());
					break;
				case '%':
					if (numberOfColons == 0)
					{
						return false;
					}
					numberOfPercent++;

					// validate that the stuff after the % is valid
					if (i + 1 >= length)
					{
						// in this case the percent is there but no number is
						// available
						return false;
					}
					try
					{
						Integer.parseInt(ipAddress.substring(i + 1));
					}
					catch (NumberFormatException e)
					{
						// right now we just support an integer after the % so
						// if
						// this is not
						// what is there then return
						return false;
					}
					break;

				default:
					if (numberOfPercent == 0)
					{
						if (word != null && word.length() > 3)
						{
							return false;
						}
						if (!isValidHexChar(c))
						{
							return false;
						}
					}
					word.append(c);
			}
		}

		// Check if we have an IPv4 ending
		if (numberOfPeriods > 0)
		{
			// There is a test case with 7 colons and valid ipv4 this should
			// resolve it
			if (numberOfPeriods != 3
			        || !(isValidIp4Word(word.toString()) && numberOfColons < 7))
			{
				return false;
			}
		}
		else
		{
			// If we're at then end and we haven't had 7 colons then there is a
			// problem unless we encountered a doubleColon
			if (numberOfColons != 7 && !doubleColon)
			{
				return false;
			}

			// If we have an empty word at the end, it means we ended in either
			// a : or a .
			// If we did not end in :: then this is invalid
			if (numberOfPercent == 0)
			{
				if (word.length() == 0
				        && ipAddress.charAt(length - 1 - offset) == ':'
				        && ipAddress.charAt(length - 2 - offset) != ':')
				{
					return false;
				}
			}
		}

		return true;
	}

	public static boolean isValidIp4Word(String word)
	{
		char c;
		if (word.length() < 1 || word.length() > 3)
		{
			return false;
		}
		for (int i = 0; i < word.length(); i++)
		{
			c = word.charAt(i);
			if (!(c >= '0' && c <= '9'))
			{
				return false;
			}
		}
		if (Integer.parseInt(word) > 255)
		{
			return false;
		}
		return true;
	}

	static boolean isValidHexChar(char c)
	{
		return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a'
		        && c <= 'f';
	}

	/**
	 * Takes a string and parses it to see if it is a valid IPV4 address.
	 * 
	 * @return true, if the string represents an IPV4 address in dotted
	 *         notation, false otherwise
	 */
	public static boolean isValidIpV4Address(String value)
	{

		int periods = 0;
		int i;
		int length = value.length();

		if (length > 15)
		{
			return false;
		}
		char c;
		StringBuilder word = new StringBuilder();
		for (i = 0; i < length; i++)
		{
			c = value.charAt(i);
			if (c == '.')
			{
				periods++;
				if (periods > 3)
				{
					return false;
				}
				if (word.length() == 0)
				{
					return false;
				}
				if (Integer.parseInt(word.toString()) > 255)
				{
					return false;
				}
				word.delete(0, word.length());
			}
			else if (!Character.isDigit(c))
			{
				return false;
			}
			else
			{
				if (word.length() > 2)
				{
					return false;
				}
				word.append(c);
			}
		}

		if (word.length() == 0 || Integer.parseInt(word.toString()) > 255)
		{
			return false;
		}
		if (periods != 3)
		{
			return false;
		}
		return true;
	}

	public static boolean isIPV6Address(String address)
	{
		try
		{
			return InetAddress.getByName(address) instanceof Inet6Address;
		}
		catch (Throwable e)
		{
			return false;
		}
	}
	
	public static boolean checkIp(String sip)
	{
		String[] parts = sip.split("\\.");
		for (String s : parts)
		{
			try
			{
				int i = Integer.parseInt(s);
				if (i < 0 || i > 255)
				{
					return false;
				}
			}
			catch (Exception e)
			{
				return false;
			}
		}
		return true;
	}
	
	public static long ipv42Int(String ip)
	{
		String[] addrArray = ip.split("\\.");
		
		long num = 0;
		for (int i = 0; i < addrArray.length; i++)
		{
			int power = 3 - i;
			
			num += ((Integer.parseInt(addrArray[i]) % 256 * Math
			        .pow(256, power)));
		}
		return num;
	}
	
	public static String int2Ipv4(long i)
	{
		return ((i >> 24) & 0xFF) + "." + ((i >> 16) & 0xFF) + "."
		        + ((i >> 8) & 0xFF) + "." + (i & 0xFF);
	}
	
	/**
	 * 10.0.0.0 ~ 10.255.255.255 172.16.0.0 ~ 172.31.255.255 192.168.0.0 ~
	 * 192.168.255.255
	 * 
	 * @param ip
	 * @return
	 */
	public static boolean isPrivateIP(String ip)
	{
		try
		{
			if (!Character.isDigit(ip.charAt(0)))
			{
				return false;
			}
			long value = ipv42Int(ip);
			if ((value >= 0x0A000000l && value <= 0x0AFFFFFFl)
			        || (value >= 0xAC100000l && value <= 0xAC1FFFFFl)
			        || (value >= 0xC0A80000l && value <= 0xC0A8FFFFl)
			
			)
			{
				return true;
			}
			return false;
		}
		catch (Exception e)
		{
			return false;
		}
	}
	
	private static String	macAddress	= null;
	
	public static String getMacAddress()
	{
		if (null != macAddress)
		{
			return macAddress;
		}
		try
		{
			byte[] mac = NetworkInterface.getByInetAddress(
			        InetAddress.getLocalHost()).getHardwareAddress();
			StringBuffer sb = new StringBuffer();
			
			for (int i = 0; i < mac.length; i++)
			{
				if (i != 0)
				{
					sb.append("-");
				}
				String s = Integer.toHexString(mac[i] & 0xFF);
				sb.append(s.length() == 1 ? 0 + s : s);
			}
			macAddress = sb.toString();
		}
		catch (Exception e)
		{
			macAddress = RandomHelper.generateRandomString(8);
		}
		return macAddress;
	}
	
	public static byte[] httpGet(String url, Proxy proxy) throws Exception
	{
		URL u = new URL(url);
		if(null == proxy)
		{
			proxy = Proxy.NO_PROXY;
		}
		try {
			HttpURLConnection conn = (HttpURLConnection) u.openConnection(proxy);
			conn.setRequestMethod("GET");
			conn.connect();
			if(conn.getResponseCode() > 300)
			{
				return null;
			}
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			byte[] buffer = new byte[8192];
			while(true)
			{
				try {
					int n = conn.getInputStream().read(buffer);
					if(n > 0)
					{
						bos.write(buffer, 0, n);
					}else{
						break;
					}
				} catch (Exception e) {
					break;
				}
			}
			return bos.toByteArray();
			
		} catch (Exception e) {
			throw e;
		}
	}
}
